package com.xuan.friend.manager;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.pagehelper.PageHelper;
import com.xuan.common.core.constants.CacheConstants;
import com.xuan.common.core.constants.Constants;
import com.xuan.common.core.enums.ContestZSetType;
import com.xuan.common.core.exception.BusinessException;
import com.xuan.common.redis.service.RedisService;
import com.xuan.friend.domain.contest.Contest;
import com.xuan.friend.domain.contest.dto.ContestQueryDTO;
import com.xuan.friend.domain.contest.dto.ContestRankDTO;
import com.xuan.friend.domain.contest.vo.ContestRankVo;
import com.xuan.friend.domain.contest.vo.ContestVo;
import com.xuan.friend.domain.contestQuestion.ContestQuestion;
import com.xuan.friend.mapper.contest.ContestMapper;
import com.xuan.friend.mapper.contest.ContestQuestionMapper;
import com.xuan.friend.mapper.user.UserContestMapper;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
public class ContestCacheManager {

    @Resource
    private RedisService redisService;

    @Resource
    private ContestMapper contestMapper;

    @Resource
    private UserContestMapper userContestMapper;

    @Resource
    private ContestQuestionMapper contestQuestionMapper;


    public Long getContestZSetSize(Integer type, Long userId, ContestQueryDTO contestQueryDTO) {
        String startTime = contestQueryDTO.getStartTime();
        String endTime = contestQueryDTO.getEndTime();
        String zSetKey = getContestZSetKey(type, userId);
        if (StringUtils.isAllBlank(startTime, endTime)) {
            return redisService.getZSetSize(zSetKey);
        } else {
            return redisService.getZSetRangeSize(zSetKey, contestQueryDTO.getStartEpochSecond(), contestQueryDTO.getEndEpochSecond());
        }

    }

    public Long getContestQuestionListSize(Long contestId) {
        String questionListKey = getContestQuestionListKey(contestId);
        return redisService.getListSize(questionListKey);
    }

    public Long getContestRankListSize(Long contestId) {
        String contestRankListKey = getContestRankListKey(contestId);
        return redisService.getListSize(contestRankListKey);
    }


    /**
     * 刷新全量数据到竞赛缓存中 (竞赛详情 和 竞赛id列表)
     *
     * @param listType
     * @param userId
     */
    public void refreshContestCache(Integer listType, Long userId) {
        List<Contest> contestList;
        if (ContestZSetType.CONTEST_UNFINISHED.getValue().equals(listType)) {
            // 查询未完赛的竞赛
            contestList = contestMapper.selectList(new LambdaQueryWrapper<Contest>()
                    .select(Contest::getContestId, Contest::getTitle, Contest::getStartTime, Contest::getEndTime)
                    .eq(Contest::getStatus, Constants.TRUE)
                    .gt(Contest::getEndTime, LocalDateTime.now())
                    .orderByDesc(Contest::getStartTime));

        } else if (ContestZSetType.CONTEST_HISTORY.getValue().equals(listType)) {
            // 查询已结束的竞赛
            contestList = contestMapper.selectList(new LambdaQueryWrapper<Contest>()
                    .select(Contest::getContestId, Contest::getTitle, Contest::getStartTime, Contest::getEndTime)
                    .eq(Contest::getStatus, Constants.TRUE)
                    .le(Contest::getEndTime, LocalDateTime.now())
                    .orderByDesc(Contest::getStartTime));
        } else {
            // 查询个人竞赛列表
            List<ContestVo> contestVoList = userContestMapper.listUserContest(userId);
            contestList = BeanUtil.copyToList(contestVoList, Contest.class);
        }
        if (CollUtil.isEmpty(contestList)) {
            return;
        }

        // 刷新数据到 redis 中(竞赛详情 和 zSet)
        Set<ZSetOperations.TypedTuple<Long>> contestIdSet = new HashSet<>();
        Map<String, Contest> contestDetailMap = new HashMap<>();
        for (Contest contest : contestList) {
            contestDetailMap.put(getContestDetailKey(contest.getContestId()), contest);
            // 将竞赛开始时间的时间戳作为 score
            Long contestId = contest.getContestId();
            double score = (double) contest.getStartTime().atZone(ZoneId.of(Constants.ZONE_ID)).toEpochSecond();
            contestIdSet.add(ZSetOperations.TypedTuple.of(contestId, score));

        }
        // 存储竞赛详细信息
        redisService.multiSet(contestDetailMap);
        // 存储某种竞赛的id
        redisService.deleteObject(getContestZSetKey(listType, userId));
        redisService.zAddAll(getContestZSetKey(listType, userId), contestIdSet);
        if (userId != null) {
            // 查询用户参加的竞赛列表时,设置过期时间
            redisService.expire(getContestZSetKey(listType, userId), CacheConstants.USER_CONTEST_EXP, TimeUnit.HOURS);
        }
    }

    /**
     * 刷新竞赛题目缓存(设置过期时间)
     *
     * @param contestId
     */
    public void refreshContestQuestionCache(Long contestId) {
        List<Long> contestQuestionIdList = contestQuestionMapper.selectList(new LambdaQueryWrapper<ContestQuestion>()
                        .select(ContestQuestion::getQuestionId)
                        .eq(ContestQuestion::getContestId, contestId)
                        .orderByAsc(ContestQuestion::getQuestionOrder))
                .stream().map(ContestQuestion::getQuestionId).toList();
        if (CollectionUtils.isEmpty(contestQuestionIdList)) {
            return;
        }

        // 得到今天的日期 + 1,并转化为 明天的午夜00:00，即今天的24:00
        LocalDateTime midnight = LocalDateTime.now().toLocalDate().plusDays(1).atStartOfDay();
        // 计算当前时间到当天 24:00 的持续时间
        Duration duration = Duration.between(LocalDateTime.now(), midnight);
        // 将持续时间转换为分钟
        long remainingMinutes = duration.toMinutes();
        // 此处 contest:question:list: 应该设置过期时间
        redisService.rightPushAll(getContestQuestionListKey(contestId), contestQuestionIdList);
        redisService.expire(getContestQuestionListKey(contestId), remainingMinutes, TimeUnit.MINUTES);
    }

    /**
     * 刷新竞赛排名缓存(设置过期时间)
     *
     * @param contestId
     */
    public void refreshContestRankCache(Long contestId) {
        List<ContestRankVo> contestRankVoList = userContestMapper.listRank(contestId);
        if (CollectionUtils.isEmpty(contestRankVoList)) {
            return;
        }
        // 得到今天的日期 + 1,并转化为 明天的午夜00:00，即今天的24:00
        LocalDateTime midnight = LocalDateTime.now().toLocalDate().plusDays(1).atStartOfDay();
        // 计算当前时间到当天 24:00 的持续时间
        Duration duration = Duration.between(LocalDateTime.now(), midnight);
        // 将持续时间转换为分钟
        long remainingMinutes = duration.toMinutes();
        // 此处 contest:rank:list: 应该设置过期时间
        redisService.rightPushAll(getContestRankListKey(contestId), contestRankVoList);
        redisService.expire(getContestRankListKey(contestId), remainingMinutes, TimeUnit.MINUTES);
    }


    public List<ContestVo> getContestVoList(ContestQueryDTO contestQueryDTO, Long userId) {
        String startTime = contestQueryDTO.getStartTime();
        String endTime = contestQueryDTO.getEndTime();
        Integer type = contestQueryDTO.getType();
        Integer pageNum = contestQueryDTO.getPageNum();
        Integer pageSize = contestQueryDTO.getPageSize();
        // 1. 计算下标
        long start = (long) (pageNum - 1) * pageSize;
        long end = start + pageSize - 1;     // redis的list类型是左闭右闭的
        List<Long> contestIdList;
        if (StringUtils.isAllBlank(startTime, endTime)) {
            contestIdList = redisService.reverseRangeForZSet(getContestZSetKey(type, userId), start, end, Long.class);
        } else {
            double startEpochSecond = contestQueryDTO.getStartEpochSecond();
            double endEpochSecond = contestQueryDTO.getEndEpochSecond();
            contestIdList = redisService.reverseRangeByScoreForZSet(getContestZSetKey(type, userId), startEpochSecond, endEpochSecond, start, pageSize, Long.class);
        }
        // 2. 根据 竞赛id 获取竞赛详情
        List<ContestVo> contestVoList = assembleContestVoList(contestIdList);
        if (CollUtil.isEmpty(contestIdList)) {
            // redis 数据存在异常 或 没有数据, 从数据库中查询
            contestVoList = getContestVoByDB(contestQueryDTO, userId);
            refreshContestCache(type, userId);    // 刷新数据到缓存中
        }
        return contestVoList;
    }


    public List<ContestRankVo> getContestRankVoList(ContestRankDTO contestRankDTO) {
        Long contestId = contestRankDTO.getContestId();
        Integer pageNum = contestRankDTO.getPageNum();
        Integer pageSize = contestRankDTO.getPageSize();
        // 1. 计算下标
        long start = (long) (pageNum - 1) * pageSize;
        long end = start + pageSize - 1;     // redis的list类型是左闭右闭的
        // 2. 获取指定区间的排名详情
        List<ContestRankVo> contestRankVoList = redisService.getCacheListByRange(getContestRankListKey(contestId), start, end, ContestRankVo.class);
        if (CollUtil.isEmpty(contestRankVoList)) {
            // redis 数据存在异常 或 没有数据, 从数据库中查询
            contestRankVoList = getContestRankByDB(contestRankDTO);
            refreshContestRankCache(contestId);    // 刷新数据到缓存中
        }
        return contestRankVoList;
    }


    public void addUserContestCache(Long userId, Long contestId) {
        String listKey = getUserContestZSetKey(userId);
        long epochSecond = LocalDateTime.now().atZone(ZoneId.of(Constants.ZONE_ID)).toEpochSecond();
        redisService.zAdd(listKey, contestId, epochSecond);
    }

    public Long getContestFirstQuestionId(Long contestId) {
        String questionListKey = getContestQuestionListKey(contestId);
        return redisService.indexForList(questionListKey, 0L, Long.class);
    }


    public Long prevQuestion(Long questionId, Long contestId) {
        String questionListKey = getContestQuestionListKey(contestId);
        Long index = redisService.indexOfForList(questionListKey, questionId);
        if (index == 0) {
            throw new BusinessException("当前已经是第一题了哦");
        }
        return redisService.indexForList(questionListKey, index - 1, Long.class);
    }

    public Long nextQuestion(Long questionId, Long contestId) {
        String questionListKey = getContestQuestionListKey(contestId);
        Long index = redisService.indexOfForList(questionListKey, questionId);
        long lastIndex = getContestQuestionListSize(contestId) - 1;
        if (index == lastIndex) {
            throw new BusinessException("当前已经是最后一题了哦");
        }
        return redisService.indexForList(questionListKey, index + 1, Long.class);
    }


    private List<ContestVo> assembleContestVoList(List<Long> contestIdList) {
        if (CollUtil.isEmpty(contestIdList)) {
            return null;
        }
        // 拼接竞赛详情 key, 利用一条命令进行多个查询
        List<String> contestDetailKeyList = new ArrayList<>();
        for (Long contestId : contestIdList) {
            contestDetailKeyList.add(getContestDetailKey(contestId));
        }
        List<ContestVo> contestVoList = redisService.multiGet(contestDetailKeyList, ContestVo.class);
        CollUtil.removeNull(contestVoList);
        if (CollUtil.isEmpty(contestVoList) || contestVoList.size() != contestIdList.size()) {
            // redis 数据存在异常,某些数据查询不到
            return null;
        }
        return contestVoList;
    }


    private List<ContestVo> getContestVoByDB(ContestQueryDTO contestQueryDTO, Long userId) {
        PageHelper.startPage(contestQueryDTO.getPageNum(), contestQueryDTO.getPageSize());
        if (userId == null) {
            // 查询 C 端竞赛列表
            return contestMapper.listContest(contestQueryDTO);
        } else {
            // 查询我的竞赛列表
            return userContestMapper.listUserContest(userId);
        }
    }

    private List<ContestRankVo> getContestRankByDB(ContestRankDTO contestRankDTO) {
        PageHelper.startPage(contestRankDTO.getPageNum(), contestRankDTO.getPageSize());
        return userContestMapper.listRank(contestRankDTO.getContestId());
    }


    /**
     * 根据 type 获取对应列表的 key
     *
     * @param type
     * @return
     */
    private String getContestZSetKey(Integer type, Long userId) {
        if (ContestZSetType.CONTEST_UNFINISHED.getValue().equals(type)) {
            // 未完赛竞赛列表
            return CacheConstants.CONTEST_UNFINISHED_ZSET_KEY;
        } else if (ContestZSetType.CONTEST_HISTORY.getValue().equals(type)) {
            // 历史竞赛列表
            return CacheConstants.CONTEST_HISTORY_ZSET_KEY;
        } else if (ContestZSetType.USER_CONTEST.getValue().equals(type)) {
            // 用户参加的竞赛列表
            return CacheConstants.USER_CONTEST_ZSET_KEY + userId;
        }
        return "";
    }


    private String getContestDetailKey(Long contestId) {
        return CacheConstants.CONTEST_DETAIL_KEY + contestId;
    }

    private String getContestQuestionListKey(Long contestId) {
        return CacheConstants.CONTEST_QUESTION_LIST_KEY + contestId;
    }

    private String getUserContestZSetKey(Long userId) {
        return CacheConstants.USER_CONTEST_ZSET_KEY + userId;
    }


    private String getContestRankListKey(Long contestId) {
        return CacheConstants.CONTEST_RANK_LIST_KEY + contestId;
    }


}
